#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using log4net.Core;
using log4net.Util;
using System;
using System.IO;

namespace log4net.Appender
{
    /// <summary>
    /// Send an email when a specific logging event occurs, typically on errors 
    /// or fatal errors. Rather than sending via smtp it writes a file into the
    /// directory specified by <see cref="PickupDir"/>. This allows services such
    /// as the IIS SMTP agent to manage sending the messages.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The configuration for this appender is identical to that of the <c>SMTPAppender</c>,
    /// except that instead of specifying the <c>SMTPAppender.SMTPHost</c> you specify
    /// <see cref="PickupDir"/>.
    /// </para>
    /// <para>
    /// The number of logging events delivered in this e-mail depend on
    /// the value of <see cref="BufferingAppenderSkeleton.BufferSize"/> option. The
    /// <see cref="SmtpPickupDirAppender"/> keeps only the last
    /// <see cref="BufferingAppenderSkeleton.BufferSize"/> logging events in its 
    /// cyclic buffer. This keeps memory requirements at a reasonable level while 
    /// still delivering useful application context.
    /// </para>
    /// </remarks>
    /// <author>Niall Daley</author>
    /// <author>Nicko Cadell</author>
    public class SmtpPickupDirAppender : BufferingAppenderSkeleton
    {
        #region Public Instance Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        /// <remarks>
        /// <para>
        /// Default constructor
        /// </para>
        /// </remarks>
        public SmtpPickupDirAppender()
        {
        }

        #endregion Public Instance Constructors

        #region Public Instance Properties

        /// <summary>
        /// Gets or sets a semicolon-delimited list of recipient e-mail addresses.
        /// </summary>
        /// <value>
        /// A semicolon-delimited list of e-mail addresses.
        /// </value>
        /// <remarks>
        /// <para>
        /// A semicolon-delimited list of e-mail addresses.
        /// </para>
        /// </remarks>
        public string To
        {
            get { return m_to; }
            set { m_to = value; }
        }

        /// <summary>
        /// Gets or sets the e-mail address of the sender.
        /// </summary>
        /// <value>
        /// The e-mail address of the sender.
        /// </value>
        /// <remarks>
        /// <para>
        /// The e-mail address of the sender.
        /// </para>
        /// </remarks>
        public string From
        {
            get { return m_from; }
            set { m_from = value; }
        }

        /// <summary>
        /// Gets or sets the subject line of the e-mail message.
        /// </summary>
        /// <value>
        /// The subject line of the e-mail message.
        /// </value>
        /// <remarks>
        /// <para>
        /// The subject line of the e-mail message.
        /// </para>
        /// </remarks>
        public string Subject
        {
            get { return m_subject; }
            set { m_subject = value; }
        }

        /// <summary>
        /// Gets or sets the path to write the messages to.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Gets or sets the path to write the messages to. This should be the same
        /// as that used by the agent sending the messages.
        /// </para>
        /// </remarks>
        public string PickupDir
        {
            get { return m_pickupDir; }
            set { m_pickupDir = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="SecurityContext"/> used to write to the pickup directory.
        /// </summary>
        /// <value>
        /// The <see cref="SecurityContext"/> used to write to the pickup directory.
        /// </value>
        /// <remarks>
        /// <para>
        /// Unless a <see cref="SecurityContext"/> specified here for this appender
        /// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the
        /// security context to use. The default behavior is to use the security context
        /// of the current thread.
        /// </para>
        /// </remarks>
        public SecurityContext SecurityContext
        {
            get { return m_securityContext; }
            set { m_securityContext = value; }
        }

        #endregion Public Instance Properties

        #region Override implementation of BufferingAppenderSkeleton

        /// <summary>
        /// Sends the contents of the cyclic buffer as an e-mail message.
        /// </summary>
        /// <param name="events">The logging events to send.</param>
        /// <remarks>
        /// <para>
        /// Sends the contents of the cyclic buffer as an e-mail message.
        /// </para>
        /// </remarks>
        override protected void SendBuffer(LoggingEvent[] events)
        {
            // Note: this code already owns the monitor for this
            // appender. This frees us from needing to synchronize again.
            try
            {
                string filePath = null;
                StreamWriter writer = null;

                // Impersonate to open the file
                using (SecurityContext.Impersonate(this))
                {
                    filePath = Path.Combine(m_pickupDir, SystemInfo.NewGuid().ToString("N"));
                    writer = File.CreateText(filePath);
                }

                if (writer == null)
                {
                    ErrorHandler.Error("Failed to create output file for writing [" + filePath + "]", null, ErrorCode.FileOpenFailure);
                }
                else
                {
                    using (writer)
                    {
                        writer.WriteLine("To: " + m_to);
                        writer.WriteLine("From: " + m_from);
                        writer.WriteLine("Subject: " + m_subject);
                        writer.WriteLine("");

                        string t = Layout.Header;
                        if (t != null)
                        {
                            writer.Write(t);
                        }

                        for (int i = 0; i < events.Length; i++)
                        {
                            // Render the event and append the text to the buffer
                            RenderLoggingEvent(writer, events[i]);
                        }

                        t = Layout.Footer;
                        if (t != null)
                        {
                            writer.Write(t);
                        }

                        writer.WriteLine("");
                        writer.WriteLine(".");
                    }
                }
            }
            catch (Exception e)
            {
                ErrorHandler.Error("Error occurred while sending e-mail notification.", e);
            }
        }

        #endregion Override implementation of BufferingAppenderSkeleton

        #region Override implementation of AppenderSkeleton

        /// <summary>
        /// Activate the options on this appender. 
        /// </summary>
        /// <remarks>
        /// <para>
        /// This is part of the <see cref="IOptionHandler"/> delayed object
        /// activation scheme. The <see cref="ActivateOptions"/> method must 
        /// be called on this object after the configuration properties have
        /// been set. Until <see cref="ActivateOptions"/> is called this
        /// object is in an undefined state and must not be used. 
        /// </para>
        /// <para>
        /// If any of the configuration properties are modified then 
        /// <see cref="ActivateOptions"/> must be called again.
        /// </para>
        /// </remarks>
        override public void ActivateOptions()
        {
            base.ActivateOptions();

            if (m_securityContext == null)
            {
                m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
            }

            using (SecurityContext.Impersonate(this))
            {
                m_pickupDir = ConvertToFullPath(m_pickupDir.Trim());
            }
        }

        /// <summary>
        /// This appender requires a <see cref="Layout"/> to be set.
        /// </summary>
        /// <value><c>true</c></value>
        /// <remarks>
        /// <para>
        /// This appender requires a <see cref="Layout"/> to be set.
        /// </para>
        /// </remarks>
        override protected bool RequiresLayout
        {
            get { return true; }
        }

        #endregion Override implementation of AppenderSkeleton

        #region Protected Static Methods

        /// <summary>
        /// Convert a path into a fully qualified path.
        /// </summary>
        /// <param name="path">The path to convert.</param>
        /// <returns>The fully qualified path.</returns>
        /// <remarks>
        /// <para>
        /// Converts the path specified to a fully
        /// qualified path. If the path is relative it is
        /// taken as relative from the application base 
        /// directory.
        /// </para>
        /// </remarks>
        protected static string ConvertToFullPath(string path)
        {
            return SystemInfo.ConvertToFullPath(path);
        }

        #endregion Protected Static Methods

        #region Private Instance Fields

        private string m_to;
        private string m_from;
        private string m_subject;
        private string m_pickupDir;

        /// <summary>
        /// The security context to use for privileged calls
        /// </summary>
        private SecurityContext m_securityContext;

        #endregion Private Instance Fields
    }
}
